Terraform 実行元の IAM 情報を取得したい時は aws_iam_session_context が使えます

Terraform 実行元の IAM 情報を取得したい時は aws_iam_session_context が使えます

Clock Icon2024.12.26

こんにちは!AWS 事業本部コンサルティング部のたかくに(@takakuni_)です。

完全に備忘録です。

Terraform でデプロイ先のアカウント ID を取得する際に data ブロックの aws_caller_identity を利用することが多いのではないでしょうか。

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity

私は以下のように、いつも使うテンプレートのようなものを用意して、検証などを行っています。

他の tf ファイルからは local.account_id を参照すればいいため、 data.aws_caller_identity.self.account_id を都度参照するより文字数が少なく可読負荷が低く済みます。便利ですよね。

providers.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.81.0"
    }
  }
}

provider "aws" {
  # Configuration options
  region = "ap-northeast-1"
}

data "aws_caller_identity" "self" {}
data "aws_region" "current" {}

locals {
  account_id = data.aws_caller_identity.self.account_id
  region     = data.aws_region.current.name
  prefix     = "sample"
}

最近、 OpenSearch Serverless や Kubernetes など、 AWS と AWS 以外の Provider を組み合わせる検証機会が増えてきました。

https://registry.terraform.io/providers/opensearch-project/opensearch/latest/docs

https://registry.terraform.io/providers/hashicorp/kubernetes/latest

商用環境などの場合、OpenSearch Serverless, Kubernetes の操作は必ずしも AWS と一致しないため、デプロイのタイミングやパイプライン、操作する IAM の権限を分けて変更を行うと思いますが、検証であれば、ササっと同一のタイミングでデプロイしてしたいケースがあるのではないでしょうか。

その場合、OpenSearch Serverless だとデータアクセスポリシー、 EKS だと ConfigMap やアクセスエントリが挙げられます。

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/opensearchserverless_access_policy

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_access_entry

では、Terraform で実行元の IAM をどのように認識すれば良いでしょうか。

data.aws_caller_identity

まずは data.aws_caller_identity の出力値をみてみましょう。

data "aws_caller_identity" "this" {}

output "aws_caller_identity" {
  value = data.aws_caller_identity.this
}

実はアカウント ID 以外も情報を取得できます。

IAM ユーザーは利用しているユーザーの ARN、IAM ロールの場合はセッション名が含まれた状態の ARN が出力されていますね。

# IAM ユーザー
aws_caller_identity = {
  "account_id" = "123456789012"
  "arn" = "arn:aws:iam::123456789012:user/takakuni"
  "id" = "123456789012"
  "user_id" = "AIDAXA25AMYQIWW25MA33"
}

# IAM ロール
aws_caller_identity = {
  "account_id" = "123456789012"
  "arn" = "arn:aws:sts::123456789012:assumed-role/takakuni/botocore-session-1735198227"
  "id" = "123456789012"
  "user_id" = "AROAXXXXXXXXXXXXXXXXX:botocore-session-1735198227"
}

data.aws_caller_identity.self.arn を利用

そのため、以下のように data.aws_caller_identity.self.arn と入れてあげるのが良さそうに思えます。

########################################################
# Data Access Policy
########################################################
resource "aws_opensearchserverless_access_policy" "this_data" {
  name        = "${local.prefix}-data"
  type        = "data"
  description = "${local.prefix}-data"
  policy = jsonencode([
    {
      Rules = [
        {
          ResourceType = "collection",
          Resource = [
            "collection/${local.prefix}-collection"
          ],
          Permission = [
            "aoss:DescribeCollectionItems",
            "aoss:CreateCollectionItems",
            "aoss:UpdateCollectionItems",
            "aoss:DeleteCollectionItems"
          ]
        },
        {
          ResourceType = "index",
          Resource = [
            "index/${local.prefix}-collection/*"
          ],
          Permission = [
            "aoss:ReadDocument",
            "aoss:WriteDocument",
            "aoss:DescribeIndex",
            "aoss:CreateIndex",
            "aoss:UpdateIndex",
            "aoss:DeleteIndex"
          ],
        },
      ],
      Principal = [
        data.aws_caller_identity.self.arn
      ]
    }
  ])
}

リソースの作成には成功します。ただ、 IAM ロールのセッション名が変わった場合、どうなるでしょうか?

正解は OpenSearch プロバイダーで定義したリソースへの読み取りアクセスが弾かれてしまいます。

原因は OpenSearch プロバイダーが既存のリソースを変更されたセッション名で参照しようとするため、データアクセスポリシーを更新する前にエラーになります。

  # aws_opensearchserverless_access_policy.this_data will be updated in-place
  ~ resource "aws_opensearchserverless_access_policy" "this_data" {
        id             = "sample-data"
        name           = "sample-data"
      ~ policy         = jsonencode(
          ~ [
              ~ {
                  ~ Principal = [
                      ~ "arn:aws:sts::123456789012:assumed-role/takakuni/botocore-session-1735197741" -> "arn:aws:sts::123456789012:assumed-role/takakuni/botocore-session-1735200718",
                    ]
                    # (1 unchanged attribute hidden)
                },
            ]
        )
      ~ policy_version = "MTczNTE5OTg5MjkyMF8y" -> (known after apply)
        # (2 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.
╷
│ Error: elastic: Error 403 (Forbidden): User does not have permissions for the requested resource [type=authorization_exception]
│
│   with opensearch_index.this,
│   on opensesarch.tf line 114, in resource "opensearch_index" "this":114: resource "opensearch_index" "this" {

これだと検証が止まってしまいますね。

ちなみに EKS の場合は assume-role ではなく role 名を指定するように怒られます。

Terraform will perform the following actions:

  # aws_eks_access_entry.this will be created
  + resource "aws_eks_access_entry" "this" {
      + access_entry_arn  = (known after apply)
      + cluster_name      = "sample-eks-cluster"
      + created_at        = (known after apply)
      + id                = (known after apply)
      + kubernetes_groups = (known after apply)
      + modified_at       = (known after apply)
      + principal_arn     = "arn:aws:sts::123456789012:assumed-role/takakuni/botocore-session-1735200718"
      + tags_all          = (known after apply)
      + type              = "STANDARD"
      + user_name         = (known after apply)
    }

  # aws_eks_access_policy_association.this will be created
  + resource "aws_eks_access_policy_association" "this" {
      + associated_at = (known after apply)
      + cluster_name  = "sample-eks-cluster"
      + id            = (known after apply)
      + modified_at   = (known after apply)
      + policy_arn    = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy"
      + principal_arn = "arn:aws:iam::123456789012:role/takakuni"

      + access_scope {
          + type = "cluster"
        }
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_eks_access_policy_association.this: Creating...
aws_eks_access_entry.this: Creating...
aws_eks_access_policy_association.this: Creation complete after 1s [id=sample-eks-cluster#arn:aws:iam::123456789012:role/takakuni#arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy]
╷
│ Error: creating EKS Access Entry (sample-eks-cluster:arn:aws:sts::123456789012:assumed-role/takakuni/botocore-session-1735200718): operation error EKS: CreateAccessEntry, https response error StatusCode: 400, RequestID: 93436721-5ba7-4d95-9ca3-9fd97f7e2080, InvalidParameterException: The principalArn parameter format is not valid
│
│   with aws_eks_access_entry.this,
│   on eks.tf line 143, in resource "aws_eks_access_entry" "this":143: resource "aws_eks_access_entry" "this" {
│
╵

You can't use the STS session principal type with access entries because this is a temporary principal for each session and not a permanent identity that can be assigned permissions.

https://docs.aws.amazon.com/eks/latest/APIReference/API_CreateAccessEntry.html#AmazonEKS-CreateAccessEntry-request-principalArn

ワイルドカードを使う

少し荒技ですが、 assume-role の指定が許されている OpenSearch の方はワイルドカードを使ってセッション名の変更をカバーできます。

ただ、複数の関数の組み合わせで 1 回で理解は難しいですね。もっとスマートな方法はないのでしょうか。

########################################################
# Data Access Policy
########################################################
resource "aws_opensearchserverless_access_policy" "this_data" {
  name        = "${local.prefix}-data"
  type        = "data"
  description = "${local.prefix}-data"
  policy = jsonencode([
    {
      Rules = [
        {
          ResourceType = "collection",
          Resource = [
            "collection/${local.prefix}-collection"
          ],
          Permission = [
            "aoss:DescribeCollectionItems",
            "aoss:CreateCollectionItems",
            "aoss:UpdateCollectionItems",
            "aoss:DeleteCollectionItems"
          ]
        },
        {
          ResourceType = "index",
          Resource = [
            "index/${local.prefix}-collection/*"
          ],
          Permission = [
            "aoss:ReadDocument",
            "aoss:WriteDocument",
            "aoss:DescribeIndex",
            "aoss:CreateIndex",
            "aoss:UpdateIndex",
            "aoss:DeleteIndex"
          ],
        },
      ],
      Principal = [
        "${join("/", slice(tolist(split("/", data.aws_caller_identity.self.arn)), 0, 2))}/*"
      ]
    }
  ])
}

aws_iam_session_context

お待たせしました。 aws_iam_session_context の出番です。

この data ブロックは実行元の IAM のセッション情報を取得するリソースです。

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_session_context

aws_iam_session_context のすごいところは、実行元の IAM が IAM ユーザーでも IAM ロールでもシームレスに動くところです。

data "aws_caller_identity" "this" {}

data "aws_iam_session_context" "this" {
  arn = data.aws_caller_identity.this.arn
}

output "aws_iam_session_context" {
  value = data.aws_iam_session_context.this
}

以下が aws_iam_session_context を使ったセッション情報の取得結果です。

issuer_arn を指定すれば、発行元の IAM ロール名を取得したい要件が満たせそうです。

# IAM ロールの場合
aws_iam_session_context = {
  "arn" = "arn:aws:sts::123456789012:assumed-role/takakuni/botocore-session-1735198227"
  "id" = "arn:aws:sts::123456789012:assumed-role/takakuni/botocore-session-1735198227"
  "issuer_arn" = "arn:aws:iam::123456789012:role/takakuni"
  "issuer_id" = "AROAXXXXXXXXXXXXXXXXX"
  "issuer_name" = "takakuni"
  "session_name" = "botocore-session-1735198227"
}

# IAM ユーザーの場合
aws_iam_session_context = {
  "arn" = "arn:aws:iam::123456789012:user/takakuni"
  "id" = "arn:aws:iam::123456789012:user/takakuni"
  "issuer_arn" = "arn:aws:iam::123456789012:user/takakuni"
  "issuer_id" = ""
  "issuer_name" = ""
  "session_name" = ""
}

aws_iam_session_context を使ってみました。非常にスッキリしましたね。

data "aws_caller_identity" "self" {}
data "aws_iam_session_context" "this" {
  arn = data.aws_caller_identity.self.arn
}

###################################################
# EKS Cluster Access Entries
###################################################
resource "aws_eks_access_entry" "this" {
  cluster_name  = aws_eks_cluster.this.name
  principal_arn = data.aws_iam_session_context.this.issuer_arn
  # principal_arn = data.aws_caller_identity.self.arn
  # principal_arn = "${join("/", slice(tolist(split("/", data.aws_caller_identity.self.arn)), 0, 2))}/*"
  type = "STANDARD"
}

resource "aws_eks_access_policy_association" "this" {
  cluster_name  = aws_eks_cluster.this.name
  principal_arn = aws_eks_access_entry.this.principal_arn
  policy_arn    = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy"

  access_scope {
    type = "cluster"
  }
}

########################################################
# Data Access Policy
########################################################
resource "aws_opensearchserverless_access_policy" "this_data" {
  name        = "${local.prefix}-data"
  type        = "data"
  description = "${local.prefix}-data"
  policy = jsonencode([
    {
      Rules = [
        {
          ResourceType = "collection",
          Resource = [
            "collection/${local.prefix}-collection"
          ],
          Permission = [
            "aoss:DescribeCollectionItems",
            "aoss:CreateCollectionItems",
            "aoss:UpdateCollectionItems",
            "aoss:DeleteCollectionItems"
          ]
        },
        {
          ResourceType = "index",
          Resource = [
            "index/${local.prefix}-collection/*"
          ],
          Permission = [
            "aoss:ReadDocument",
            "aoss:WriteDocument",
            "aoss:DescribeIndex",
            "aoss:CreateIndex",
            "aoss:UpdateIndex",
            "aoss:DeleteIndex"
          ],
        },
      ],
      Principal = [
        data.aws_iam_session_context.this.issuer_arn
      ]
    }
  ])
}

EKS のアクセスエントリも上手く作れていますし、キャッシュ削除後の OpenSearch が上手く読み込めていることがわかります。

takakuni.shinnosuke@HL01556 sagemaker_hyperpod_eks % terraform apply
╷
│ Warning: Provider development overrides are in effect
│
│ The following provider development overrides are set in the CLI configuration:- hashicorp.com/edu/hashicups in /Users/takakuni.shinnosuke/.asdf/installs/golang/1.23.2/packages/bin
│
│ The behavior may therefore not match any released version of the provider and applying changes may cause the state to become incompatible with published releases.data.aws_caller_identity.self: Reading...
data.aws_region.current: Reading...
data.aws_iam_policy_document.hyperpod_vpc: Reading...
# 省略

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_eks_access_entry.this will be created
  + resource "aws_eks_access_entry" "this" {
      + access_entry_arn  = (known after apply)
      + cluster_name      = "sample-eks-cluster"
      + created_at        = (known after apply)
      + id                = (known after apply)
      + kubernetes_groups = (known after apply)
      + modified_at       = (known after apply)
      + principal_arn     = "arn:aws:iam::123456789012:role/takakuni"
      + tags_all          = (known after apply)
      + type              = "STANDARD"
      + user_name         = (known after apply)
    }

  # aws_eks_access_policy_association.this will be created
  + resource "aws_eks_access_policy_association" "this" {
      + associated_at = (known after apply)
      + cluster_name  = "sample-eks-cluster"
      + id            = (known after apply)
      + modified_at   = (known after apply)
      + policy_arn    = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy"
      + principal_arn = "arn:aws:iam::123456789012:role/takakuni"

      + access_scope {
          + type = "cluster"
        }
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_eks_access_entry.this: Creating...
aws_eks_access_entry.this: Creation complete after 1s [id=sample-eks-cluster:arn:aws:iam::123456789012:role/takakuni]
aws_eks_access_policy_association.this: Creating...
aws_eks_access_policy_association.this: Creation complete after 1s [id=sample-eks-cluster#arn:aws:iam::123456789012:role/takakuni#arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Outputs:

cluster_name = "sample-eks-cluster"
# キャッシュの削除
takakuni.shinnosuke@HL01556 sagemaker_hyperpod_eks % rm -r ~/.aws/cli/cache
takakuni.shinnosuke@HL01556 sagemaker_hyperpod_eks % terraform apply
╷
│ Warning: Provider development overrides are in effect
│
│ The following provider development overrides are set in the CLI configuration:- hashicorp.com/edu/hashicups in /Users/takakuni.shinnosuke/.asdf/installs/golang/1.23.2/packages/bin
│
│ The behavior may therefore not match any released version of the provider and applying changes may cause the state to become incompatible with published releases.
╵
Enter MFA code for arn:aws:iam::123456789012:mfa/takakuni:
data.aws_region.current: Reading...
data.aws_caller_identity.self: Reading...
data.aws_iam_policy_document.hyperpod_vpc: Reading...
aws_opensearchserverless_security_policy.this_network: Refreshing state... [id=sample-ntwrk]
aws_opensearchserverless_security_policy.this_encryption: Refreshing state... [id=sample-enc]
aws_vpc_endpoint.s3: Refreshing state... [id=vpce-01b62e5aaa1164609]
# 省略

# OpenSearch のリソースを読み込めている
opensearch_index.this: Refreshing state... [id=sample-vector-index]
aws_eks_cluster.this: Refreshing state... [id=sample-eks-cluster]
module.vpc.aws_route.private_nat_gateway[0]: Refreshing state... [id=r-rtb-0827b15aecf4e62ee1080289494]
module.vpc.aws_route.private_nat_gateway[1]: Refreshing state... [id=r-rtb-0dcfee08b12998fdd1080289494]
aws_eks_access_entry.this: Refreshing state... [id=sample-eks-cluster:arn:aws:iam::123456789012:role/takakuni]
aws_eks_addon.kube_proxy: Refreshing state... [id=sample-eks-cluster:kube-proxy]
aws_eks_addon.vpc_cni: Refreshing state... [id=sample-eks-cluster:vpc-cni]
aws_eks_addon.eks_pod_identity_agent: Refreshing state... [id=sample-eks-cluster:eks-pod-identity-agent]
aws_eks_access_policy_association.this: Refreshing state... [id=sample-eks-cluster#arn:aws:iam::123456789012:role/takakuni#arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

cluster_name = "sample-eks-cluster"

おわりに

以上、「Terraform 実行元の IAM 情報を取得したい時は aws_iam_session_context が使えます」でした。

EKS を久しぶりに触り、気がつけばアクセスエントリという概念が増えていました。

最近、実行元の IAM を知りたい時が Open Search Serverless の検証でよくあり、ワイルドカード戦法を使っていたのですが、今一度どれが正解なのだ?と調べていると辿り着けました。

EKS Module さん、いつもありがとうございます。

https://github.com/terraform-aws-modules/terraform-aws-eks/blob/master/main.tf

このブログがどなたかの参考になれば幸いです。

AWS 事業本部コンサルティング部のたかくに(@takakuni_)でした!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.